using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Text;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using ErkerTech.DeltaScope.Engine;
using ErkerTech.DeltaScope.View.Controls;
using ErkerTech.DeltaScope.View.Models;

namespace ErkerTech.DeltaScope.View
{
    public class SwirlController
    {
        #region Private Declarations

        private Bitmap _canvas;
        private SwirlDisplay _target;

        private int _unitSize = 10;			// WORK AS A CONSTANT FOR NOW... LATER MAKE IT Font.GetHeight() / 2
        private bool _bNeedToCalculate = true;	// When this is true we need to recalculate.
        private int _swirlAreaWidth;			// The width of the Target control.
        private int _lastLeftMove;
        private int _lastRightMove;
        private int _totalLeftMove;
        private int _totalRightMove;
        private int _bottomLeft;
        private int _bottomRight;

        private TextRenderingHint _textRendHint = TextRenderingHint.SystemDefault;

        private int _lastMoveIndex = -1;		
        private int _movesCount = -1;
        private int[,] _moveArray;
        private bool _bNoMoreMoves;
        private readonly IList<MoveStruct> _moves = new List<MoveStruct>();
        private readonly IList<int> _jumpTo = new List<int>();  // Store the move index for each block...

        private int[,] _startingLineIndexes;
        private int[,] _lineLengths;
        private DeltaState[] _states;
        #endregion

        // Read in the list of DeltaBlocks...
        public void Render(IList<DeltaBlock> blocks)
        {
            DeltaBlock loBlock;
            var lnBlockCount = blocks.Count;

            // Setup the lookup arrays...
            _startingLineIndexes = new int[lnBlockCount, 2];
            _lineLengths = new int[lnBlockCount, 2];
            _states = new DeltaState[lnBlockCount];

            // add the strings to both sides...
            for (var lnCnt = 0; lnCnt < lnBlockCount; lnCnt++)
            {
                loBlock = blocks[lnCnt];
                _states[lnCnt] = loBlock.DiffState;
                _lineLengths[lnCnt, 0] = loBlock.Left.Count;
                _lineLengths[lnCnt, 1] = loBlock.Right.Count;
                if (lnCnt == 0)
                {
                    _startingLineIndexes[lnCnt, 0] = 1;
                    _startingLineIndexes[lnCnt, 1] = 1;
                }
                else
                {
                    _startingLineIndexes[lnCnt, 0] = (_lineLengths[lnCnt-1, 0] + _startingLineIndexes[lnCnt-1, 0]);
                    _startingLineIndexes[lnCnt, 1] = (_lineLengths[lnCnt-1, 1] + _startingLineIndexes[lnCnt-1, 1]);
                }
            }
        }

        #region Properties
        public IList<SwirlBlock> Blocks { get; set; }
        public Font Font
        {
            get { return _target.Font; }
            set	{ _target.Font = value; }
        }
        public int JumpCount { get { return _jumpTo.Count; } }
        public SmoothingMode SmoothingMode { get; set; }
        public int SwirlPoint	// The 35% marker...
        {
            get { return (int) Math.Ceiling(Target.ClientRectangle.Height * 0.35); }
        }
        public SwirlDisplay Target
        {
            get {return _target;}
            set 
            {
                _target = value;
                _swirlAreaWidth = (value != null) ? _target.SwirlAreaWidth : 0;
            }
        }
        public TextRenderingHint TextRenderingHint
        {
            get { return _textRendHint; }
            set { _textRendHint = value; }
        }
        public bool WordWrap
        {
            get { return SwirlBlock.WordWrap; }
            set { SwirlBlock.WordWrap = value; }
        }
		
        #endregion

        #region Constructors
        public SwirlController()
        {
            SmoothingMode = SmoothingMode.Default;
        }
        public SwirlController(SwirlDisplay toTarget): this()
        {
            Target = toTarget;
        }
        public SwirlController( SwirlDisplay toTarget, IList<SwirlBlock> toBlocks) : this(toTarget)
        {
            Blocks = toBlocks;
        }
        #endregion

        #region Event Handlers
        private void TargetDisposed(object sender, EventArgs e)
        {
            if (_canvas != null) _canvas.Dispose();
        }

        private void DrawSwirls(object sender, PaintEventArgs e)
        {
            // If we don't have a Last Move index then just draw from the top...
            if (_lastMoveIndex <= 0)
            {
                Draw(e.Graphics);
                return;
            }
            // Recalculate the dimensions and move amounts...
            Calculate(e.Graphics);

            // Make sure the LastMove Index doesn't move us out of bounds...
            if (_movesCount == 0)
            {
                _lastMoveIndex = 0;
                Draw(e.Graphics);
                return;
            }

            if (_movesCount < _lastMoveIndex) 
                _lastMoveIndex = _movesCount;

            // Keep the scrollbar in synch and move the Display to the approximately the same move amount...
            _target.VScroller.Value = _lastMoveIndex;
            Move(_lastMoveIndex);
        }

        private void VScrollerScrollHandler(object sender, ScrollEventArgs e)
        {
            if (e.NewValue == _target.VScroller.Value) return;
            Move(e.NewValue);
        }

        private void TargetRedrawHandler(object swirlDisplay, EventArgs e)
        {
            _bNeedToCalculate = true;
            Draw();
        }

        private void TargetMouseWheelHandler(object sender, MouseEventArgs e)
        {
            var loVScroller = _target.VScroller;
            var lnNewValue = ((( e.Delta * loVScroller.LargeChange) / 120) * -1);
            lnNewValue += loVScroller.Value;

            if ((lnNewValue < 0) || ((loVScroller.Maximum - loVScroller.LargeChange + 1) < lnNewValue)) return;
            loVScroller.Value = lnNewValue;
            Move(lnNewValue);
        }

        private void TargetInvalidatedHandler(object sender, InvalidateEventArgs e)
        {
            _bNeedToCalculate = true;
        }
        #endregion

        #region Private Methods
        private void BuildMoveArray()
        {
            _movesCount = _moves.Count;
            if (_movesCount == 0)
            {
                _moveArray = new int[0,0];
                return;
            }
            _moveArray = new int[_movesCount, 2];

            var lnLeftTotal = 0;
            var lnRightTotal = 0;
            MoveStruct loMove;
            for (var lnCnt = 0; lnCnt < _movesCount; lnCnt++)
            {	
                loMove = _moves[lnCnt];
                lnLeftTotal += loMove.LeftMove;
                lnRightTotal += loMove.RightMove;
                _moveArray[lnCnt, 0] = lnLeftTotal;
                _moveArray[lnCnt, 1] = lnRightTotal;
            }
            // Clean up some memory...
            _lastLeftMove = 0;
            _lastRightMove = 0;
            _moves.Clear();
        }

        private void CalculateMoveAmounts(int tnSwirlPoint)
        {
            // If there are no blocks then exit now...
            if (Blocks.Count <= 0) return;

            // Keep lists of the moves made by both sides...
            _moves.Clear();
            _jumpTo.Clear();

            _totalLeftMove = 0;
            _totalRightMove = 0;
            _bNoMoreMoves = false;

            // Go through all the blocks...
            var lnLAmountToSwirl = 0;
            var lnRAmountToSwirl = 0;
            var lnTotalLAmount = 0;
            var lnTotalRAmount = 0;
            for (var lnBlockIdx = 0; lnBlockIdx < Blocks.Count; lnBlockIdx++)
            {
                // Get the left & right rectangles adjusted to where they would be if they were really moved into this position...
                var leftRect = Blocks[lnBlockIdx].LeftSide.Rect;
                var rightRect = Blocks[lnBlockIdx].RightSide.Rect;

                // Total all the moves from both sides into the side variables...
                lnTotalLAmount += (lnLAmountToSwirl * -1);
                lnTotalRAmount += (lnRAmountToSwirl * -1); 
                leftRect.Y += lnTotalLAmount;
                rightRect.Y += lnTotalRAmount; 

                //work out how many moves to get the Left past the Swirl point... 
                lnLAmountToSwirl = leftRect.Bottom - tnSwirlPoint;
                lnRAmountToSwirl = rightRect.Bottom - tnSwirlPoint;
                var lnLMovesToMake = (int) Math.Floor((double)lnLAmountToSwirl / _unitSize);
                var lnRMovesToMake = (int) Math.Floor((double)lnRAmountToSwirl / _unitSize);
				
                if (lnLMovesToMake <= 0 && lnRMovesToMake <= 0)
                {
                    // These blocks are above the swirl point, nothing to move...
                    lnLAmountToSwirl = 0;
                    lnRAmountToSwirl = 0;
					
                    // The index into the array is the same as the Block Index.
                    // Store -1 to Flag that there is no moves for this block...
                    _jumpTo.Add(0);	
                }
                else if (lnLMovesToMake > 0 && lnRMovesToMake <= 0)
                {
                    // The Right Side is ABOVE the swirl point, but the Left Side is below it.
                    // Move the LeftSide up until the LeftSide bottom matches the RightSide.
                    lnRAmountToSwirl = 0;
                    lnLAmountToSwirl = leftRect.Bottom - rightRect.Bottom;
                    lnLMovesToMake = (int) Math.Floor((double)lnLAmountToSwirl / _unitSize);
                    var lnRemainder = (lnLAmountToSwirl - (lnLMovesToMake * _unitSize));
                    WriteMovesToArray(lnLMovesToMake, _unitSize, 0, lnRemainder, 0);
                }
                else if ((lnLMovesToMake <= 0) && (lnRMovesToMake > 0))
                {
                    // The Left Side is ABOVE the swirl point and the Right Side is below it.
                    // Move the RightSide up until the RightSide bottom matches the LeftSide.
                    lnLAmountToSwirl = 0;
                    lnRAmountToSwirl = rightRect.Bottom - leftRect.Bottom;
                    lnRMovesToMake = (int) Math.Floor((double)lnRAmountToSwirl / _unitSize);
                    var lnRemainder = (lnRAmountToSwirl - (lnRMovesToMake * _unitSize));
                    WriteMovesToArray(lnRMovesToMake, 0, _unitSize, 0, lnRemainder);
                }
                else if (lnLMovesToMake > lnRMovesToMake)
                {
                    var lnRAmountToMove = (lnRAmountToSwirl / lnLMovesToMake);
                    var lnLRemainder = (lnLAmountToSwirl - (lnLMovesToMake * _unitSize));
                    var lnRRemainder = (lnRAmountToSwirl - (lnLMovesToMake * lnRAmountToMove));

                    // Loop through the Right sides remainder and distribute it 1 pixel at a time...
                    WriteMovesToArray(lnRRemainder, _unitSize, lnRAmountToMove+1, 0, 0);
                    WriteMovesToArray(lnLMovesToMake-lnRRemainder, _unitSize, lnRAmountToMove, lnLRemainder, 0);
                }
                else if (lnRMovesToMake > lnLMovesToMake)
                {
                    var lnLAmountToMove = (lnLAmountToSwirl / lnRMovesToMake);
                    var lnLRemainder = (lnLAmountToSwirl - (lnRMovesToMake * lnLAmountToMove));
                    var lnRRemainder = (lnRAmountToSwirl - (lnRMovesToMake * _unitSize));

                    WriteMovesToArray(lnLRemainder, lnLAmountToMove+1, _unitSize, 0, 0);
                    WriteMovesToArray(lnRMovesToMake - lnLRemainder, lnLAmountToMove, _unitSize, 0, lnRRemainder);
                }
                else if (lnLMovesToMake == lnRMovesToMake)
                {
                    // handle the remainders... If both remainder are 0, then don't add the move...
                    var lnLRemainder = (lnLAmountToSwirl - (lnLMovesToMake * _unitSize));
                    var lnRRemainder = (lnRAmountToSwirl - (lnRMovesToMake * _unitSize));
                    WriteMovesToArray(lnRMovesToMake, _unitSize, _unitSize, lnLRemainder, lnRRemainder);
                }
            }
        }
        private void SetScrollBar(ScrollBar toVScroll)
        {
            // The total number of moves to make...
            toVScroll.Minimum = 0;
            toVScroll.Maximum = _movesCount;
            toVScroll.Value = 0;
            toVScroll.SmallChange = 1;			
            toVScroll.LargeChange = (_movesCount > 5) ? 5: 1;

            // The User can never get to the VScrollbar's Maximum, because .NET treats the Maximum
            // as ([Moves.Count] - LargeChange) + 1. So to adjust for this, I need to add the (LargeChange-1) to 
            // my value.
            toVScroll.Maximum += (toVScroll.LargeChange - 1);
            toVScroll.Visible = (toVScroll.Maximum > 0);
        }

        private void WriteMovesToArray (int tnLoopCnt, int tnLAmount, int tnRAmount, int tnLRemainder, int tnRRemainder)
        {	
            var lnTargetBottom = _target.DisplayRectangle.Bottom-2;
            for (var lnCnt = 0; lnCnt < tnLoopCnt; lnCnt++)
            {
                _totalLeftMove += tnLAmount;
                _totalRightMove += tnRAmount;
                if ((((_bottomLeft - _totalLeftMove - lnTargetBottom) <=0) && ((_bottomRight - _totalRightMove - lnTargetBottom) <= 0)))
                {
                    if (!_bNoMoreMoves)
                    {
                        var lnLeft = (_bottomLeft - _totalLeftMove - lnTargetBottom) * -1;
                        var lnRight = (_bottomRight - _totalRightMove - lnTargetBottom) * -1;

                        lnLeft = (lnLeft < tnLAmount) ? tnLAmount - lnLeft: 0;
                        lnRight = (lnRight < tnRAmount) ? tnRAmount - lnRight: 0;
                        if ((lnLeft != 0) || (lnRight != 0)) _moves.Add(new MoveStruct(lnLeft, lnRight));
                    }
                    _bNoMoreMoves = true;
                }
                if (!_bNoMoreMoves) _moves.Add(new MoveStruct(tnLAmount, tnRAmount));
            }
			
            // If both remainders are 0, then don't bother...
            if (tnLRemainder == 0 && tnRRemainder == 0) return;

            _totalLeftMove += tnLRemainder;
            _totalRightMove += tnRRemainder;
            if ((((_bottomLeft - _totalLeftMove - lnTargetBottom) <=0) && ((_bottomRight - _totalRightMove - lnTargetBottom) <= 0)))
            {
                if (!_bNoMoreMoves)
                {
                    var lnLeft = (_bottomLeft - _totalLeftMove - lnTargetBottom) * -1;
                    var lnRight = (_bottomRight - _totalRightMove - lnTargetBottom) * -1;

                    lnLeft = (lnLeft < tnLAmount) ? lnLeft: 0;
                    lnRight = (lnRight < tnRAmount) ? lnRight: 0;
                    if ((lnLeft != 0) || (lnRight != 0)) _moves.Add(new MoveStruct(lnLeft, lnRight));
                }
                _bNoMoreMoves = true;
            }
            if (!_bNoMoreMoves) _moves.Add(new MoveStruct(tnLRemainder, tnRRemainder));

            // Jump to the Last move allowed for this block...
            _jumpTo.Add(_moves.Count);
        }
        #endregion

        #region Public Methods
        public bool Bind()
        {
            var lbResult = (_target != null);
            if (lbResult) 
            {
                _target.Disposed += TargetDisposed;
                _target.Paint += DrawSwirls;	
                _target.Invalidated += TargetInvalidatedHandler;
                _target.Redraw += TargetRedrawHandler;
                _target.MouseWheel += TargetMouseWheelHandler;
                _target.VScroller.Scroll += VScrollerScrollHandler;
            }
            return lbResult;
        }
		
        public void Calculate()
        {
            // Get a handle to the grapchis engine...
            var loGraphics = _target.CreateGraphics();
            Calculate(loGraphics);
            loGraphics.Dispose();
        }

        public void Calculate(Graphics toGraphics)
        {
            // exit out if re-calculating is not needed...
            if (_bNeedToCalculate == false) return;
            _bNeedToCalculate = false;

            // raise the Will Calculate event...
            var loTarget = _target;
            loTarget.OnCalculate(true);

            if (Blocks.Count <= 0) return;

            // Get the Swirl Point from the screen just once...
            var lnSwirlPoint = SwirlPoint;

            // Move 1 line at a time...
            _unitSize = (int) Math.Floor(loTarget.Font.GetHeight());

            // Get the value to calculate with...
            var lnLeftTop = 0;
            var lnRightTop = 0;
            var lnWidth = loTarget.DrawWidth;
            var lnSwirlAreaWidth = loTarget.SwirlAreaWidth;

            // Turn on the smoothing mode, to make sure the real heights are calculated...
            toGraphics.Clear(loTarget.BackColor);
            toGraphics.SmoothingMode = SmoothingMode;
            toGraphics.TextRenderingHint = _textRendHint;

            // Go through all the blocks in order and have them calculate...				
            for (var lnCnt = 0; lnCnt < Blocks.Count; lnCnt++)
            {
                Blocks[lnCnt].Calculate(toGraphics, Font, lnWidth, lnSwirlAreaWidth, lnLeftTop, lnRightTop);
                lnLeftTop  = Blocks[lnCnt].LeftSide.Rect.Bottom;
                lnRightTop = Blocks[lnCnt].RightSide.Rect.Bottom;
            }

            // If we can show all the blocks in the display and we had the scrollbar visible, then recalculate
            // OR, if we didn't have the scrollbar visible and now we need to scroll then recalculate...
            var lnDispBottom = loTarget.DisplayRectangle.Bottom;
            var loVScroll = loTarget.VScroller;
            if (
                ((lnLeftTop  <= lnDispBottom && lnRightTop <= lnDispBottom) && loVScroll.Visible) ||
                ((lnLeftTop  > lnDispBottom || lnRightTop > lnDispBottom) && loVScroll.Visible == false))
            {
                loVScroll.Visible = !(loVScroll.Visible);
                lnLeftTop = 0; lnRightTop = 0;
                lnWidth = loTarget.DrawWidth;
                for (var lnCnt = 0; lnCnt < Blocks.Count; lnCnt++)
                {
                    Blocks[lnCnt].Calculate(toGraphics, Font, lnWidth, lnSwirlAreaWidth, lnLeftTop, lnRightTop);
                    lnLeftTop  = Blocks[lnCnt].LeftSide.Rect.Bottom;
                    lnRightTop = Blocks[lnCnt].RightSide.Rect.Bottom;
                }
            }

            // Hold onto those values...
            _bottomLeft = lnLeftTop;
            _bottomRight = lnRightTop;

            // Create a working canvas...
            _canvas = new Bitmap(lnWidth, loTarget.DisplayRectangle.Height);

            // Work out the movement array...
            CalculateMoveAmounts(lnSwirlPoint);
            BuildMoveArray();
            SetScrollBar(loVScroll);

            // Raise the DidCalculate event...
            loTarget.OnCalculate(false);
        }
        public void Draw()
        {
            var loGraphics = _target.CreateGraphics();
            Draw(loGraphics);
            loGraphics.Dispose();
        }

        public void Draw(Graphics toGraphics)
        {
            //Re calculate the Swirlblocks if necessary...
            Calculate(toGraphics);
			
            var lnListCount = Blocks.Count;
            if (lnListCount <= 0) return;

            // Turn on the smoothing mode...
            var loMyGraphics = Graphics.FromImage(_canvas);
            loMyGraphics.Clear(_target.BackColor);
            loMyGraphics.SmoothingMode = SmoothingMode;
            loMyGraphics.TextRenderingHint = _textRendHint;

            var lnDispBottom = _target.DisplayRectangle.Bottom;
            SwirlBlock loBlock;
            for (var lnCnt = 0; lnCnt < lnListCount; lnCnt++)
            {
                loBlock = Blocks[lnCnt];
                if ((loBlock.LeftSide.Rect.Top > lnDispBottom) && (loBlock.RightSide.Rect.Top > lnDispBottom)) break;
                loBlock.DrawBlock(loMyGraphics, (loBlock.LeftSide.Rect.Bottom >= 0), (loBlock.RightSide.Rect.Bottom >= 0));
            }

            //Draw the lines around the swirl region...
            if (_target.ShowSwirlRect)
            {						  
                var lnX = Blocks[0].LeftSide.Rect.Width;
                loMyGraphics.DrawLine(new Pen(_target.ForeColor), lnX, 0, lnX, _target.ClientRectangle.Height);

                lnX = Blocks[0].RightSide.Rect.Left;
                loMyGraphics.DrawLine(new Pen(_target.ForeColor), lnX, 0, lnX, _target.ClientRectangle.Height);
            }

            // Paint the Bitmap to the screen...
            toGraphics.DrawImageUnscaled(_canvas, _target.DisplayRectangle);
            loMyGraphics.Dispose();
        }
		
        public void JumpToBlock(int tnBlockIdx)
        {
            // do bounds checking...
            if (_jumpTo.Count <= tnBlockIdx) return;

            // Move to the first Move index of a SwirlBlock...
            var lnMoveIndex = (int)_jumpTo[tnBlockIdx];
            if (_movesCount < lnMoveIndex) return;

            Move(lnMoveIndex);
            _target.VScroller.Value = lnMoveIndex;
        }

        public void Move (int tnMoveIndex)
        {
            // Do bounds checking...
            if (tnMoveIndex < 0 || _movesCount == 0) return;

            // Total up the amount to move each side by...
            var lnTotalMoveLeft = 0;
            var lnTotalMoveRight = 0;

            // Move each side...
            var lnMoveIdx = (tnMoveIndex-1);
            if (lnMoveIdx >= 0)
            {
                lnTotalMoveLeft = _moveArray[lnMoveIdx, 0];
                lnTotalMoveRight = _moveArray[lnMoveIdx, 1];
            }

            for (var lnCnt = 0; lnCnt < Blocks.Count; lnCnt++)
            {
                Blocks[lnCnt].LeftSide.MoveRectTop(lnTotalMoveLeft + (_lastLeftMove * -1));
                Blocks[lnCnt].RightSide.MoveRectTop(lnTotalMoveRight + (_lastRightMove * -1));
            }

            // Store the moves we just made...
            _lastLeftMove = lnTotalMoveLeft;
            _lastRightMove = lnTotalMoveRight;
            _lastMoveIndex = tnMoveIndex;

            // Force the display to redraw correctly...
            Draw();
        }

        public bool UnBind()
        {
            var lbResult = (_target != null);
            if (lbResult) 
            {
                _target.Disposed -=TargetDisposed;
                _target.Paint -= DrawSwirls;
                _target.Invalidated -= TargetInvalidatedHandler;
                _target.Redraw -= TargetRedrawHandler;
                _target.MouseWheel -= TargetMouseWheelHandler;
                _target.VScroller.Scroll -= VScrollerScrollHandler;
            }
            return lbResult;
        }

        #endregion
    }
}